package com.tebiecloud.common.security;

import com.tebiecloud.common.properties.CommonProperties;
import com.tebiecloud.common.utils.BeanConvertUtils;
import com.tebiecloud.common.utils.ReflectionUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import org.springframework.util.Assert;

import java.util.Collection;
import java.util.Collections;
import java.util.Map;

/**
 * 认证信息帮助类
 * @Author: tebie
 * @Date: 2019-07-12 10:53
 */
@Slf4j
public class SecurityHelper {

    /**
     * 获取认证用户信息
     * @return
     */
    public static SecurityUserDetails getUser() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null && authentication.isAuthenticated() && authentication instanceof OAuth2Authentication) {
            OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) authentication;
            OAuth2Request clientToken = oAuth2Authentication.getOAuth2Request();
            if (!oAuth2Authentication.isClientOnly()) {
                if (authentication.getPrincipal() instanceof SecurityUserDetails) {
                    return (SecurityUserDetails) authentication.getPrincipal();
                }
                if (authentication.getPrincipal() instanceof Map) {
                    return BeanConvertUtils.mapToObject((Map) authentication.getPrincipal(), SecurityUserDetails.class);
                }
            } else {
                SecurityUserDetails securityUserDetails = new SecurityUserDetails();
                securityUserDetails.setClientId(clientToken.getClientId());
                securityUserDetails.setAuthorities(clientToken.getAuthorities());
                return securityUserDetails;
            }
        }
        return null;
    }

    /**
     * 更新用户信息
     * @param tokenStore
     * @param securityUserDetails
     */
    public void updateSecurityUser(TokenStore tokenStore, SecurityUserDetails securityUserDetails) {
        // 动态更新客户端生成的token
        Collection<OAuth2AccessToken> accessTokens = tokenStore.findTokensByClientIdAndUserName(securityUserDetails.getClientId(), securityUserDetails.getUsername());
        for (OAuth2AccessToken accessToken : accessTokens) {
            // 由于没有set方法,使用反射机制强制赋值
            OAuth2Authentication oAuth2Authentication = tokenStore.readAuthentication(accessToken);
            Authentication authentication = oAuth2Authentication.getUserAuthentication();
            ReflectionUtils.setFieldValue(authentication, "principal", securityUserDetails);
            // 重新保存
            tokenStore.storeAccessToken(accessToken, oAuth2Authentication);
        }
    }

    /**
     * 获取认证用户Id
     * @return
     */
    public static Long getUserId() {
        return getUser().getUserId();
    }

    /**
     * 是否拥有权限
     * @param authority
     * @return
     */
    public static Boolean hasAuthority(String authority) {
        SecurityUserDetails auth = getUser();
        if (auth == null) {
            return false;
        }
        if (AuthorityUtils.authorityListToSet(auth.getAuthorities()).contains(authority)) {
            return true;
        }
        return false;
    }

    /**
     * 构建token转换器
     * @return
     */
    public static DefaultAccessTokenConverter buildAccessTokenConverter() {
        SecurityUserConverter userAuthenticationConverter = new SecurityUserConverter();
        DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
        accessTokenConverter.setUserTokenConverter(userAuthenticationConverter);
        return accessTokenConverter;
    }

    /**
     * 构建jwtToken转换器
     * @param properties
     * @return
     * @throws Exception
     */
    public static JwtAccessTokenConverter buildJwtTokenEnhancer(CommonProperties properties) throws Exception {
        JwtAccessTokenConverter converter = new SecurityJwtAccessTokenEnhancer();
        converter.setSigningKey(properties.getJwtSigningKey());
        converter.afterPropertiesSet();
        return converter;
    }

    /**
     * 构建自定义远程Token服务类
     * @param properties
     * @return
     */
    public static RemoteTokenServices buildRemoteTokenServices(CommonProperties properties) {
        // 使用自定义系统用户凭证转换器
        DefaultAccessTokenConverter accessTokenConverter = buildAccessTokenConverter();
        RemoteTokenServices tokenServices = new RemoteTokenServices();
        tokenServices.setCheckTokenEndpointUrl(properties.getTokenInfoUri());
        tokenServices.setClientId(properties.getClientId());
        tokenServices.setClientSecret(properties.getClientSecret());
        tokenServices.setAccessTokenConverter(accessTokenConverter);
        log.info("buildRemoteTokenServices[{}]", tokenServices);
        return tokenServices;
    }

    /**
     * 构建资源服务器JwtToken服务类
     * @param properties
     * @return
     * @throws Exception
     */
    public static ResourceServerTokenServices buildJwtTokenServices(CommonProperties properties) throws Exception {
        // 使用自定义系统用户凭证转换器
        DefaultAccessTokenConverter accessTokenConverter = buildAccessTokenConverter();
        SecurityJwtTokenService tokenServices = new SecurityJwtTokenService();
        // 这里的签名key 保持和认证中心一致
        JwtAccessTokenConverter converter = buildJwtTokenEnhancer(properties);
        JwtTokenStore jwtTokenStore = new JwtTokenStore(converter);
        tokenServices.setTokenStore(jwtTokenStore);
        tokenServices.setJwtAccessTokenConverter(converter);
        tokenServices.setDefaultAccessTokenConverter(accessTokenConverter);
        log.info("buildJwtTokenServices[{}]", tokenServices);
        return tokenServices;
    }

    /**
     * 构建资源服务器RedisToken服务类
     * @param redisConnectionFactory
     * @return
     * @throws Exception
     */
    public static ResourceServerTokenServices buildRedisTokenServices(RedisConnectionFactory redisConnectionFactory) throws Exception {
        SecurityRedisTokenService tokenServices = new SecurityRedisTokenService();
        // 这里的签名key 保持和认证中心一致
        RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
        tokenServices.setTokenStore(redisTokenStore);
        log.info("buildRedisTokenServices[{}]", tokenServices);
        return tokenServices;
    }

    /**
     * 认证服务器原始方式创建AccessToken
     * @param endpoints
     * @param postParameters
     * @return
     * @throws Exception
     */
    public static OAuth2AccessToken createAccessToken(AuthorizationServerEndpointsConfiguration endpoints, Map<String, String> postParameters) throws Exception {
        Assert.notNull(postParameters.get("client_id"),"client_id not null");
        Assert.notNull(postParameters.get("client_secret"),"client_secret not null");
        UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(postParameters.get("client_id"),postParameters.get("client_secret"), Collections.emptyList());
        ResponseEntity<OAuth2AccessToken> responseEntity = endpoints.tokenEndpoint().postAccessToken(auth,postParameters);
        return  responseEntity.getBody();
    }

}
